/**
 * 
 * <p>
 * Title: HeroCombatTimer.java
 * </p>
 * <p>
 * Description:
 * </p>
 * <p>
 * Copyright: Copyright (c) 2004 Sun Microsystems, Inc.
 * </p>
 * <p>
 * Company: Sun Microsystems, Inc
 * </p>
 * 
 * @author Jeff Kesselman
 * @version 1.0
 */
package com.worldwizards;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * 
 * <p>
 * Title: HeroCombatTimer.java
 * </p>
 * <p>
 * Description:
 * </p>
 * <p>
 * Copyright: Copyright (c) 2004 Sun Microsystems, Inc.
 * </p>
 * <p>
 * Company: Sun Microsystems, Inc
 * </p>
 * 
 * @author Jeff Kesselman
 * @version 1.0
 */
public class HeroCombatTimer {
	class DexBucket implements Comparable {
		int dex;

		List<HeroPlaytimeRecord> characters;

		public DexBucket(int dex) {
			this.dex = dex;
			characters = new ArrayList<HeroPlaytimeRecord>();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Comparable#compareTo(java.lang.Object)
		 */
		public int compareTo(Object o) {
			// Note that this "backwards", lower dexes are considered
			// 'higher'
			// then hiher ones. This is because we want the list in
			// descending
			// order, not ascending, and sorted losts dont have a
			// facility for that
			return ((DexBucket) o).dex - dex;
		}

		/**
		 * @param prec
		 * @return
		 */
		public boolean contains(HeroPlaytimeRecord prec) {
			return characters.contains(prec);
		}

		/**
		 * @param prec
		 */
		public void add(HeroPlaytimeRecord prec) {
			characters.add(prec);

		}

		/**
		 * @param prec
		 */
		public void remove(HeroPlaytimeRecord prec) {
			characters.remove(prec);

		}

		public List<HeroPlaytimeRecord> getCharacters() {
			return characters;
		}
	}

	private int round = 0;

	private int segment = 0;

	private int actingIndex = 0;

	List<HeroPlaytimeRecord> charactersInCombat = new LinkedList<HeroPlaytimeRecord>();

	List<HeroPlaytimeRecord> actingThisSegment;

	private List<HeroCombatTimerListener> listeners = new ArrayList<HeroCombatTimerListener>();

	public HeroCombatTimer() {

	}

	public void addListener(HeroCombatTimerListener l) {
		listeners.add(l);
	}

	public void addCharacterToCombat(HeroPlaytimeRecord prec) {
		if (!charactersInCombat.contains(prec)) {
			charactersInCombat.add(prec);
			firePlayerAddedToCombat(prec);
		}
		makeSegmentActionList();
		reportSegmentActors();
	}

	/**
	 * @param prec
	 */
	private void firePlayerAddedToCombat(HeroPlaytimeRecord prec) {
		for (HeroCombatTimerListener l : listeners) {
			l.playerAddedToCombat(prec);
		}

	}

	public void removeCharacterFromCombat(HeroPlaytimeRecord prec) {
		if (charactersInCombat.contains(prec)) {
			charactersInCombat.remove(prec);
			firePlayerRemovedFromCombat(prec);
		}
		makeSegmentActionList();
		reportSegmentActors();
	}

	/**
	 * @param prec
	 */
	private void firePlayerRemovedFromCombat(HeroPlaytimeRecord prec) {
		for (HeroCombatTimerListener l : listeners) {
			l.playerRemovedFromCombat(prec);
		}

	}

	public void startCombat() {
		segment = 11;
		round = 0;
		advanceSegment();
	}

	/**
	 * 
	 */
	public void advanceSegment() {
		segment++;
		// bottom of 12
		if (segment > 12) {
			fireEndOfRound(round);
			round++;
			segment = 1;
		}
		fireSegmentChange(round, segment);
		makeSegmentActionList();
		reportSegmentActors();
	}

	private void makeSegmentActionList() {
		// make Dex Buckets
		SortedSet<DexBucket> dexBuckets = new TreeSet<DexBucket>();
		for (HeroPlaytimeRecord rec : charactersInCombat) {
			int dex = rec.getActingDex();
			DexBucket bucket = getBucket(dexBuckets, dex);
			bucket.add(rec);
		}
		// now make segment actors list
		actingThisSegment = new ArrayList<HeroPlaytimeRecord>();
		List<HeroPlaytimeRecord> shuffleList = new LinkedList<HeroPlaytimeRecord>();
		for (DexBucket bucket : dexBuckets) {
			shuffleList.clear();
			for (HeroPlaytimeRecord rec : bucket.getCharacters()) {
				shuffleList.add(
						(int) (Math.random() * (shuffleList.size() + 1)), rec);
			}

			for (HeroPlaytimeRecord rec : shuffleList) {
				if (!rec.isDead() && !rec.isUnconcious()
						&& rec.actsOnSegment(segment)) {
					actingThisSegment.add(rec);
				}
			}
		}
		actingIndex = -1;
		advanceActingCharacter();
	}

	private DexBucket getBucket(SortedSet<DexBucket> dexBuckets, int dex) {
		for (DexBucket bucket : dexBuckets) {
			if (bucket.dex == dex) {
				return bucket;
			}
		}
		DexBucket bucket = new DexBucket(dex);
                dexBuckets.add(bucket);
		return bucket;
	}

	public void advanceActingCharacter() {
		if (hasNoActors()) {
			return;
		}
		actingIndex++;
		if (actingIndex < actingThisSegment.size()) {
			// skip stunned
			HeroPlaytimeRecord rec = actingThisSegment.get(actingIndex);
			while ((actingIndex < actingThisSegment.size()) && rec.isStunned()) {
				rec.setStunned(false);
				actingIndex++;
				firePlaytimeRecordStateChange(rec);
			}
		}
		if (actingIndex >= actingThisSegment.size()) {
			// end of segment, go to next
			advanceSegment();
			while (actingThisSegment.size() == 0) {
				// keep advancing til someone can act.
				advanceSegment();
			}
		} else {
			fireActingIndex(actingIndex);
		}
	}

	private void firePlaytimeRecordStateChange(HeroPlaytimeRecord rec) {
		for (HeroCombatTimerListener l : listeners) {
			l.characterRecordStateChange(rec);
		}

	}

	/**
	 * @return
	 */
	private boolean hasNoActors() {
		for (HeroPlaytimeRecord rec : charactersInCombat) {
			if (!rec.canNeverAct()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @param round2
	 * @param segment2
	 */
	private void fireSegmentChange(int round, int segment2) {
		for (HeroCombatTimerListener l : listeners) {
			l.segmentChange(round, segment);
		}

	}

	public void reportSegmentActors() {

		fireActingThisSegment(actingThisSegment);
		fireActingIndex(actingIndex);

	}

	/**
	 * @param actingIndex2
	 */
	private void fireActingIndex(int actingIndex) {
		for (HeroCombatTimerListener l : listeners) {
			l.actingCharacterIndex(actingIndex);
		}

	}

	/**
	 * @param actingThisSegment
	 */
	private void fireActingThisSegment(
			List<HeroPlaytimeRecord> actingThisSegment) {
		for (HeroCombatTimerListener l : listeners) {
			l.actingThisSegment(actingThisSegment);
		}

	}

	/**
	 * @param round2
	 */
	private void fireEndOfRound(int round) {
		for (HeroCombatTimerListener l : listeners) {
			l.endOfRound(round);
		}

	}

	/**
	 * 
	 */
	public void reset() {
		round = 0;
		segment = 12;
		for (HeroPlaytimeRecord rec : charactersInCombat) {
			rec.reset();
		}
		fireSegmentChange(round, segment);
		makeSegmentActionList();
		reportSegmentActors();
	}

	/**
	 * 
	 */
	public void previousSegment() {
		segment--;
		if (segment < 1) {
			segment = 1;
		}
		fireSegmentChange(round, segment);
		makeSegmentActionList();
		reportSegmentActors();
	}

	/**
	 * @return
	 */
	public Set<HeroPlaytimeRecord> getCharacterSet() {
		return new HashSet(charactersInCombat);
	}

	public HeroPlaytimeRecord currentlyActingCharacter() {
		return actingThisSegment.get(actingIndex);
	}

	public void takeRecovery() {
		HeroPlaytimeRecord rec = currentlyActingCharacter();
		rec.takeRecovery();
		advanceActingCharacter();
	}

	public void updateCharacterState(HeroPlaytimeRecord heroPlaytimeRecord) {
		reportSegmentActors();

	}

}
